package com.mini.common.sign;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zhangxiaobin
 * @title: SignInterceptor
 * @description:
 * @date 2019/11/613:58
 */
@Component
@Slf4j
public class SignInterceptor implements HandlerInterceptor {

    @Value("${sunlands.out.api.secretkey}")
    private String secretkey;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestSig = request.getHeader("sig");
        String requestURI = request.getRequestURI();
        if(StringUtils.isBlank(requestSig)){
            log.error("签名错误， sig 为空 , requestURI = {}",requestURI);
            throw new RuntimeException("签名错误,sig值为空");
        }

        boolean checkSign = false;
        //根据不同的请求方式判断参数sig--begin
        String method = request.getMethod();
        String contentType = request.getHeader("Content-Type");
        if ("GET".equals(method)) {
            log.debug("SignInterceptor get method ,uri =【{}】, sig = 【{}】, params = 【{}】", request.getRequestURI(), requestSig, request.getQueryString());
            Map<String, String[]> requestParams =requestToMapObject(request);
            checkSign = SignUtil.verifySign(secretkey, requestSig, requestParams);
        }else if(("POST".equals(method) || "PUT".equals(method)) && contentType!=null && contentType.indexOf("multipart") < 0){
            if(contentType.indexOf("application/json") > 0){
                /*
			 * 读取请求体中的数据(字符串形式)
			 * 注:由于同一个流不能读取多次;如果在这里读取了请求体中的数据,那么@RequestBody中就不能读取到了
			 *    会抛出异常并提示getReader() has already been called for this request
			 * 解决办法:先将读取出来的流数据存起来作为一个常量属性.然后每次读的时候,都需要先将这个属性值写入,再读出.
			 *        即每次获取的其实是不同的流,但是获取到的数据都是一样的.
			 *        这里我们借助HttpServletRequestWrapper类来实现
			 *      注:此方法涉及到流的读写、耗性能;
			 *
			 */
                MyRequestWrapper mrw = new MyRequestWrapper(request);
                String bodyString = mrw.getBody();
                checkSign = SignUtil.verifySign(secretkey, requestSig, bodyString);
                log.debug("post/put method ,uri = 【{}】 , sig = 【{}】, params = 【{}】", requestURI, requestSig, ""+bodyString);
            }else{
                Map<String, String[]> requestParams =requestToMapObject(request);
                checkSign = SignUtil.verifySign(secretkey, requestSig, requestParams);
            }


        }else{
            checkSign = true;
            log.debug("SignInterceptor {} method, uri=【{}】, sig = 【{}】unhandled.", method, requestURI, requestSig);
        }

        if (!checkSign) {
            throw new RuntimeException("签名错误");
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }

    /**
     * 判断GET请求的签名
     * @param request
     * @return
     */
    private HashMap<String, String []> requestToMapObject(HttpServletRequest request) {

        HashMap<String, String []> parameterMap = new HashMap<String, String []>();
        Enumeration<String> names = request.getParameterNames();
        if (names != null) {
            Collections.list(names).stream().filter(name -> name != null).forEach(name -> {
                String value = request.getParameter(name);
                if(StringUtils.isNotEmpty(value)){
                    parameterMap.put(name.toString(), new String [] {request.getParameter(name.toString())});
                }else{
                    String[] values = request.getParameterValues(name);
                    parameterMap.put(name.toString(), values);
                }
            });
        }
        return parameterMap;
    }

}

/**
 * 辅助类 ---> 变相使得可以多次通过(不同)流读取相同数据
 */
class MyRequestWrapper extends HttpServletRequestWrapper {

    private final String body;

    public String getBody() {
        return body;
    }

    public MyRequestWrapper(final HttpServletRequest request) throws IOException {
        super(request);
        StringBuilder sb = new StringBuilder();
        String line;
        BufferedReader reader = request.getReader();
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }

        body = sb.toString();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());
        return new ServletInputStream() {
            /*
             * 重写ServletInputStream的父类InputStream的方法
             */
            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
}
